package top.shiyiri.set;

import org.junit.Test;

import java.util.*;

/**
 * @author Aunean
 * @date 2022/1/5
 */
/*
Set接口：存储无序的，不可重复的数据
    HashSet：作为Set接口的主要实现类；线程不安全的；可以存储null值
        LinkedHashSet：HashSet的子类；遍历其内部数据时，可以按照添加的顺序遍历
                       对于比较频繁的遍历操作，LinkedHashSet效率高于HashSet
    TreeSet：可以按照添加对象的指定属性进行排序。

Set接口中没有额外定义新的方法，使用的都是Collection中声明过的方法

要求：向Set中添加的数据，其所在的类一定要重写hashCode()和equals()
要求：重写的hashCode()和equals()尽可能保持一致性：相等的对象必须具有相等的散列码
重写两个方法的小技巧：对象中用作equals()方法比较的Filed，都应该用来计算hashCode值
 */
public class HashSetTest {

    /*
    Set：存储无序的、不可重复的数据
    以HashSet为例说明：
    1.无序性：不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加，而是根据数据的哈希值决定的
    2.不可重复性：保证添加的元素按照equals()判断时，不能返回true。即：相同的元素只有一个

    添加元素的过程：以HashSet为例：
        我们向 HashSet 中添加元素 a，首先调用元素 a 所在类的 hashCode()，计算元素 a
        的哈希值，此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置（即为：索引位置），
        判断数组此位置上是否已经有元素：
            如果此位置上没有其他元素，则元素 a 添加成功  --情况一
            如果此位置上有其他元素b（或以链表形式存储的多个元素），则比较元素a与元素b的的hash值：
                如果hash不相同，则元素 a 添加成功  --情况二
                如果hash值相同，进而需要调用元素 a 所在类的 equals()：
                    equals()返回true，元素 a 添加失败
                    equals()返回false，则元素 a 添加成功  --情况三
     对于添加成功的情况二和情况三而言：元素 a 与已经存在指定索引位置上的数据以链表的方式存储
     JDK7：元素 a 放到数组中，指向原来的元素
     JDK8：原来的元素放在数组中，指向元素a

     HashSet底层：数组 + 链表的结构
     */
    @Test
    public void test1() {
        Set<Object> set = new HashSet<>();
        set.add(456);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom", 12));
        set.add(new User("Tom", 12));
        set.add(129);
        set.add(123);

        System.out.println(set);
    }


    //LinkedHashSet的使用
    //LinkedHashSet作为HashSet的子类，在添加的数据的同时，每个数据还维护了两个引用，记录此数据前一个数据和后一个数据
    //优点：对于比较频繁的遍历操作，LinkedHashSet效率高于HashSet
    @Test
    public void test2() {
        Set<Object> set = new LinkedHashSet<>();
        set.add(456);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom", 12));
        set.add(new User("Tom", 12));
        set.add(129);

        System.out.println(set);

        for (Object obj: set) {
            System.out.println(obj);
        }
    }


    @Test
    public void test3() {
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");
        set.add(p1);
        set.add(p2);
        p1.name = "CC";//将 Person(id=1001, name=AA) 变成 Person(id=1001, name=CC)
        set.remove(p1);//按照 Person(id=1001, name=CC) 的哈希值进行删除
        System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC)]
        set.add(new Person(1001,"CC"));//按 Person(id=1001, name=CC) 的哈希值进行添加，若此位置没有元素，测添加成功
        System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC), Person(id=1001, name=CC)]
        set.add(new Person(1001,"AA"));//按 Person(id=1001, name=AA) 的哈希值进行添加，此位置有值，比较hash值是否相同，相同则再比较equals()，不相等则添加成功
        System.out.println(set);//[Person(id=1002, name=BB), Person(id=1001, name=CC), Person(id=1001, name=CC), Person(id=1001, name=AA)]
    }

}
